# 03/25/2011: Scott Campbell
#
# Core analyzer for analyzing basic sshd
# The utility provided by this policy is *logging* in nature.  Authentication rules,
#  local policy re. hostile commands and content, key analysis etc are all rolled
#  into their respective policy files.
#
# In addition, most of the utility functions are maintained in this file as well

@load hot-ids
@load listen-clear
@load notice
@load login

@load sshd_const
@load sshd_auth
#@load sshd_policy

module SSHD_CORE;

export {

	redef enum Notice += {
		SSHD_Heartbeat,
		SSHD_NewHeartbeat,
		SSHD_Start,
		SSHD_End,
		SSHD_PasswdThresh,
	};

	global sshd_log: file = open_log_file("sshd_core");
	global sshd_audit_log: file = open_log_file("sshd_audit");

	# global sshd index for sessions
	global s_index: count = 0;
	
	global c_record_clean: function(t: table[count] of int, idx:count) : interval;
	# function for heartbeat utility
	#global s_record_clean: function(t: table[string] of server_record, idx:string) : interval;

	######################################################################################
	#  data structs and tables
	######################################################################################
	type client_record: record {
		conn: connection;			# generated by function, handy to have
		uid: string &default = "UNKNOWN";	# value reset by login
		auth_type: string &default = "UNKNOWN";	# value reset by login
		auth_state: count &default=1;		# used to track logins
		suspicous_count: count &default = 0;	# running total for suspicous commands
		client_tag: count &default = 0;		# unique id
		start_time: time;			# 
		passwd_skip: count &default = 0;	# how many times passwd entry skipped

		# table of channel types - may need to reinsert state 
		channel_type: table[count] of string;
		s_commands: set[string];		# list of suspicous commands entered
	};

	type server_record: record {
		# put in a rate monitor here as well ..	
		c_records: table[count] of client_record;	# this is a table of client_record types
		current_clients: count;				#
		start_time: time;				#
		heartbeat_seen: count &default=0;		#
		heartbeat_last: double;				#
		heartbeat_lock: bool &default=F;		#
	};

	global s_record_clean: function(t: table[string] of server_record, idx:string) : interval;
	# this is a table holding all the known server instances
	global s_records: table[string] of server_record  &persistent &expire_func=s_record_clean &write_expire = 24 hr;

	# When a subsystem is instantiated, the process loses the cid data which is an 
	#  issue in tracking the behavior.  This table keeps track of the cid as a function
	#  of the ppid and sid - it will be set when the forking settles down post privsep.
	global cid_lookup: table[string, int] of count;

	# in order to keep track of usage, we have a table which records which events are used
	global sshd_auditor: table[string] of count;

	# functions for testing client and server records
	global test_sid: function(sid: string) : server_record;
	global test_cid: function(sid: string, cid: count) : client_record;
	# function for auditing usage
	global sshd_audit: function(call: string);
	# function for clearing strings
	global mtt: function(data: string) : string;
	# function to look up cid
	global lookup_cid: function(sid: string,ppid: int) : count;
	global print_sid: function(sid: string) : string;


	######################################################################################
	#  configuration
	#
	######################################################################################

	# suspicous commands 
	global notify_suspicous_command = T &redef;

	global suspicous_threshold: count = 5 &redef;
	global suspicous_command_list = 
		/^who/
		| /^rpcinfo/
	&redef;

	# this set of commands should be alarmed on when executed
	#  remotely
	global alarm_remote_exec =
		/sh -i/
		| /bash -i/
	&redef;

	const user_white_list =
		/^billybob$/
	&redef;

	# heartbeat timeout interval ...
	const heartbeat_timeout = 30 sec &redef;
	#const heartbeat_timeout = 300 sec &redef;

	# password skip alarm threshold
	const password_threshold = 10 &redef;

	# for line editing
	const BS = "\x08";
	const DEL = "\x7f";

} # end of export

######################################################################################
#  external values
######################################################################################

redef Remote::destinations += {
	["sshd2"] = [$host = 127.0.0.1, $events = /.*/, $connect=F, $ssl=F],
};

######################################################################################
#  functions 
######################################################################################
function create_connection(s_ip: addr, s_port: port, r_ip: addr, r_port: port, ts: time): connection
{
	local s_set: set[string];
	add s_set["ssh-login"];
	
	local c: connection;

	local id: conn_id;
	local orig: endpoint;
	local resp: endpoint;

	id$orig_h = s_ip;
	id$orig_p = s_port;
	id$resp_h = r_ip;
	id$resp_p = r_port;

	orig$size = 0;
	orig$state = 0;
	resp$size = 0;
	resp$state = 0;

	c$id = id;
	c$orig = orig;
	c$resp = resp;
	c$start_time = ts;
	c$duration = 0 sec;

	c$service = s_set;
	c$addl = "";
	c$hot = 0;

	return c;
}

function sshd_audit(call: string)
{
	# look and see if this is a new call
	if ( call !in sshd_auditor ) {
		local t_call: string = call;
		sshd_auditor[t_call] = 0;
	}

	# increment the name
	++sshd_auditor[call];

	return;
}

function test_sid(sid: string): server_record
{
	# Test to see if server record exists.  If so, return it
	#   else create a new one.
	local t_server_record: server_record;

	if ( sid ! in s_records ) {
		# this is an unknown instance so we
		# create something new
		t_server_record$current_clients = 0;
		#t_server_record$active = 1;
		t_server_record$start_time = network_time();

		s_records[sid] = t_server_record;
	}
	else {
		t_server_record = s_records[sid];
	}
	
	return t_server_record;
}

function test_cid(sid: string, cid: count): client_record
{
	# Since every cid must have a sid, first test for it.
	# When created, it will be nearly empty - we will fill it in later
	#   via the calling event.
	local t_client_rec: client_record;

	# first check the sid
	local t_server_rec = test_sid(sid);

	if ( cid !in t_server_rec$c_records ) {

		# create a new rec and insert it into the table
		# first increment the client session identifier
		++s_index;
		t_client_rec$client_tag = s_index;

		# create a blank table for channel state
		local t_cs:table[count] of string;
		t_client_rec$channel_type = t_cs;

		# now fill in the blank connection values
		t_client_rec$conn$id$orig_h = 0.0.0.0;
		t_client_rec$conn$id$orig_p = 0/tcp;
		t_client_rec$conn$id$resp_h = 0.0.0.0;
		t_client_rec$conn$id$resp_p = 0/tcp;

		t_server_rec$c_records[cid] = t_client_rec;
	}
	else {
		t_client_rec = t_server_rec$c_records[cid];
	}

	return t_client_rec;
}

# insert a cient record back into the appropriate data structure
#
function save_cid(sid: string, cid: count, cr: client_record)
{
	if ( sid in s_records ) {
		s_records[sid]$c_records[cid] = cr;
	}
}

function remove_cid(sid:string, cid:count) : int
{
	local ret: int = 1;

	if ( sid in s_records ) 

		if ( cid in s_records[sid]$c_records ) {

			# now that we have a record, start removing things
			local c: count;

			for ( c in s_records[sid]$c_records[cid]$channel_type )
				delete s_records[sid]$c_records[cid]$channel_type[c];

			delete s_records[sid]$c_records[cid];
			ret = 0;
		}
	return ret;
}

# calls remove_cid() 
function remove_sid(sid:string) : int
	{
	local ret: int = 1;
	local t_cid: count;

	if ( sid in s_records ) {

		for ( t_cid in s_records[sid]$c_records )
			remove_cid(sid, t_cid);

		delete s_records[sid];
		ret = 0;
	}

	return ret;
	}

function s_record_clean(t: table[string] of server_record, idx:string) : interval
	{
	remove_sid(idx);
	return 0 secs;
	}

function c_record_clean(t: table[count] of int, idx:count) : interval
	{
	# for the time being, see if the s-record_clean will take care of any issues
	# if not, just add another field to the client_record holding the sid
	#remove_cid(idx);
	return 0 secs;
	}

function print_sid(sid: string) : string
{
	# sid is of the form "hostname hostid port"
	local split_on_space = split(sid, /:/);

	return split_on_space[2];

}

function mtt(data: string) : string
{
	# monkey thought translator

	# normalize the data with respect to back space and ^H 
	local normalize_data = edit(data, BS);
	normalize_data = edit(normalize_data, DEL);

	return normalize_data;
}

# global lookup_cid: function(string, int) : count;
function lookup_cid(sid: string, ppid: int) : count
{
	local ret: count = 0;
	local split_cln = split(sid, /:/);
	local t_sid = fmt("%s:%s", split_cln[2], split_cln[3]);

	if ( [t_sid,ppid] in cid_lookup ) {
		ret = cid_lookup[t_sid,ppid];
		}

	return ret;
}

######################################################################################
#  events
######################################################################################

# Rather than having a bunch of events for every type of auth/meth/state, we just
#  wrap it all up into one big event.  
# Currently authmesg: {Postponed, Accepted, Failed}
# 	method: typically password, publickey, hostbased, keyboard-interactive/pam
#
# This will be designed to work with the syslog analyzer code as well as with any other
#   simple user-id based authentication schemas.
#
event auth_info_3(ts: time, version: string, sid: string, cid: count, authmsg: string, uid: string, meth: string, s_addr: addr, s_port: port, r_addr: addr, r_port: port)
{
	local CR:client_record = test_cid(sid,cid);
	local SR:server_record = test_sid(sid);

	# fill in a few additional records inthe client and server records
	CR$conn = create_connection(s_addr, s_port, r_addr, r_port, ts);
	CR$uid = uid;
	CR$auth_type = meth;
	CR$start_time = ts;

	++SR$current_clients;

	SR$c_records[cid] = CR;
	s_records[sid] = SR;

	print sshd_log, fmt("%.6f #%s - %s %s AUTH %s %s %s %s:%s > %s:%s", 
		ts, CR$client_tag, print_sid(sid), cid, authmsg, uid, meth, s_addr, s_port, r_addr, r_port);

	local ct = fmt("#%s", CR$client_tag);

	if ( to_upper(authmsg) == "ACCEPTED" ) {
		SSHD_AUTH::ssh_accept(ts, s_addr, r_addr, uid, "sshd", ct, meth);
	}

	if ( to_upper(authmsg) == "FAILED" ) {
		SSHD_AUTH::ssh_fail(ts, s_addr, r_addr, uid, "sshd", ct, meth);
	}

	if ( to_upper(authmsg) == "POSTPONED" ) { 
		SSHD_AUTH::ssh_postponed(ts, s_addr, r_addr, uid, "sshd", ct, meth);
	} 
} 


event auth_invalid_user_3(ts: time, version: string, sid: string, cid: count, uid: string)
{
	# first log this, then (when implemented) pass into the authentication module
	local CR:client_record = test_cid(sid,cid);

	print sshd_log, fmt("%.6f #%s - %s %s INVALID_USER  %s:%s > %s @ %s:%s",
		ts, CR$client_tag, print_sid(sid), cid, CR$conn$id$orig_h, CR$conn$id$orig_p, 
		CR$conn$id$resp_h, CR$conn$id$resp_p, uid);

	#SSHD_AUTH::ssh_invalid();
}


event auth_key_fingerprint_3(ts: time, version: string, sid: string, cid: count, fingerprint: string, key_type: string)
{
	local CR:client_record = test_cid(sid,cid);

	print sshd_log, fmt("%.6f #%s - %s %s AUTH_KEY_FINGERPRINT %s type %s", 
		ts, CR$client_tag, print_sid(sid), cid, fingerprint, key_type);
}

event auth_pass_attempt_3(ts: time, version: string, sid: string, cid: count, authenticated: count, uid: string, password: string)
{
	# note that this will only get called if the sshd has been configured with the 
	#  --with-passwdrec option set
	local CR:client_record = test_cid(sid,cid);
	
	print sshd_log, fmt("%.6f #%s - %s %s AUTH_PASS_ATTEMPT Authenticated: %s uid: %s pass: %s", 
		ts, CR$client_tag, print_sid(sid), cid, authenticated, uid, password);	
}

event channel_data_client_3(ts: time, version: string, sid: string, cid: count, channel:count, data:string)
{
	# general event for client data from a typical login shell
	local CR:client_record = test_cid(sid,cid);

	if ( channel !in CR$channel_type )
		{
			CR$channel_type[channel] = "unknown";
		}

	# normalize the data against back space and ^H 
	local normalize_data = mtt(data);

	print sshd_log, fmt("%.6f #%s %s-%s %s %s DATA_CLIENT %s", 
		ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid), cid, normalize_data);

}

event channel_data_server_3(ts: time, version: string, sid: string, cid: count, channel: count, data: string)
{
	# general event for client data from a typical login shell
	local CR:client_record = test_cid(sid,cid);

	if ( channel !in CR$channel_type )
		{
			CR$channel_type[channel] = "unknown";
		}

	# normalize the data against back space and ^H 
	#local normalize_data = mtt(data);

	print sshd_log, fmt("%.6f #%s %s-%s %s %s DATA_SERVER %As", 
		ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid), cid, data);
		#ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid), cid, normalize_data);

}

event channel_data_server_sum_3(ts: time, version: string, sid: string, cid: count, channel: count, bytes_skip: count)
{
	# summary of data skipped from volume/line limit
	local CR:client_record = test_cid(sid,cid);

	if ( channel !in CR$channel_type )
		{
			CR$channel_type[channel] = "unknown";
		}

	print sshd_log, fmt("%.6f #%s %s-%s %s %s DATA_SERVER_SUM_SKIP: %s", 
		ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid), cid, bytes_skip);
}


event channel_free_3(ts: time, version: string, sid: string, cid: count,channel: count, name: string)
{
	# channel free event - pass back name and number
	local CR:client_record = test_cid(sid,cid);

	if ( channel !in CR$channel_type )
		{
			CR$channel_type[channel] = "unknown";
		}

	print sshd_log, fmt("%.6f #%s %s-%s %s %s CHANNEL_FREE", 
		ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid), cid);
}

event channel_new_3(ts: time, version: string, sid: string, cid: count, found: count, ctype: count, name: string)
{
	# found: channel id
	# type: channel type including some state info, defined by ints
	# name: remote name as provided unstructured text

	local CR:client_record = test_cid(sid,cid);

	# if the value exists, throw a weird and run over it
	CR$channel_type[found] = to_lower(name);

	# the channel_name[count] = string table is in sshd_const.bro
	print sshd_log, fmt("%.6f #%s - %s %s CHANNEL_NEW [%s] %s", 
		ts, CR$client_tag, print_sid(sid), cid, found, CR$channel_type[found]);

}

event channel_notty_analysis_disable_3(ts: time, version: string, sid: string, cid: count, channel: count, byte_skip: int, byte_sent: int)
{
	# Record NOTTY_DATA_SAMPLE bytes regardless of the state of the
	#  test.  After ratio print/noprint exceeds NOTTY_BIN_RATIO.
	# This report on the results.

	local CR:client_record = test_cid(sid,cid);

	if ( channel !in CR$channel_type )
		{
			CR$channel_type[channel] = "unknown";
		}

	print sshd_log, fmt("%.6f #%s %s-%s %s %s NOTTY_ANALYSIS_DISABLE %s skip %s allow", 
		ts, CR$client_tag, channel, CR$channel_type[channel], print_sid(sid), cid, byte_skip, byte_sent);

}

event channel_notty_client_data_3(ts: time, version: string, sid: string, cid: count, channel: count, data: string)
{
	# client data via non-tty means ...

	local CR:client_record = test_cid(sid,cid);

	if ( channel !in CR$channel_type )
		{
			CR$channel_type[channel] = "unknown";
		}

	# normalize the data against back space and ^H 
	local normalize_data = mtt(data);

	print sshd_log, fmt("%.6f #%s %s-%s %s %s NOTTY_DATA_CLIENT %s", 
		ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid), cid, normalize_data);
}

event channel_notty_server_data_3(ts: time, version: string, sid: string, cid: count, channel: count, data: string)
{
	# server data via non-tty means ...

	local CR:client_record = test_cid(sid,cid);

	if ( channel !in CR$channel_type )
		{
			CR$channel_type[channel] = "unknown";
		}

	# normalize the data against back space and ^H 
	local normalize_data = mtt(data);

	print sshd_log, fmt("%.6f #%s %s-%s %s %s NOTTY_DATA_SERVER %s", 
		ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid), cid, normalize_data);
}

event channel_pass_skip_3(ts: time, version: string, sid: string, cid: count, channel: count)
{
	# Keep track of the number of times a data line is skipped
	#  in order to keep people from exploiting the password skip
	#  feature.

	local CR:client_record = test_cid(sid,cid);

	if ( channel !in CR$channel_type )
		{
			CR$channel_type[channel] = "unknown";
		}

	if ( ++CR$passwd_skip == password_threshold ) {

		NOTICE([$note=SSHD_PasswdThresh,
			$msg=fmt("#%s %s-%s %s %s %s @ %s -> %s:%s",
				CR$client_tag, channel, CR$channel_type[channel], sid, cid, CR$uid, 
				CR$conn$id$orig_h, CR$conn$id$resp_h, CR$conn$id$resp_p )]);
	}
	
	print sshd_log, fmt("%.6f #%s %s-%s %s %s CHANNEL_PASS_SKIP %s", 
		ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid), cid, password_threshold);

}

event channel_port_open_3(ts: time, version: string, sid: string, cid: count, channel: count, rtype: string, l_port: port, path: string, h_port: port, rem_host: string, rem_port: port)
{
	# rtype: type of port open { direct-tcpip, dynamic-tcpip, forwarded-tcpip }
	# l_port: port being listened for forwards
	# path: path for unix domain sockets, or host name for forwards 
	# h_port: remote port to connect for forwards
	# rem_host: remote IP addr
	# rep_port: remote port

	local CR:client_record = test_cid(sid,cid);

	if ( channel !in CR$channel_type )
		{
			CR$channel_type[channel] = "unknown";
		}

	print sshd_log, fmt("%.6f #%s %s-%s %s %s CHANNEL_PORT_OPEN listen port %s for %s %s:%s -> %s:%s",
		ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid), cid, rtype, l_port, rem_host, rem_port, path, h_port);

}
event channel_portfwd_req_3(ts: time, version: string, sid: string, cid: count, channel:count, host: string, fwd_port: count)
{
	# This is called after receiving CHANNEL_FORWARDING_REQUEST.  This initates
	#  listening for the port, and sends back a success reply (or disconnect
	#  message if there was an error).

	local CR:client_record = test_cid(sid,cid);

	if ( channel !in CR$channel_type )
		{
			CR$channel_type[channel] = "unknown";
		}

	print sshd_log, fmt("%.6f #%s %s-%s %s %s CHANNEL_PORTFWD_REQ %s:%s", 
		ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid), cid, host, fwd_port);

}

event channel_post_fwd_listener_3(ts: time, version: string, sid: string, cid: count, channel: count, l_port: port, path: string, h_port: port, rtype: string)
{
	# This socket is listening for connections to a forwarded TCP/IP port.
	#
	# rtype: type of port open { direct-tcpip, dynamic-tcpip, forwarded-tcpip }
	# l_port: port being listened for forwards
	# h_port: remote port to connect for forwards

	local CR:client_record = test_cid(sid,cid);

	if ( channel !in CR$channel_type )
		{
			CR$channel_type[channel] = "unknown";
		}

	print sshd_log, fmt("%.6f #%s %s-%s %s %s CHANNEL_PORT_FWD_LISTENER request %s %s -> %s:%s",
		ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid), 
		cid, rtype, l_port, path, h_port);
}

event channel_set_fwd_listener_3(ts: time, version: string, sid: string, cid: count, channel: count, c_type: count, wildcard: count, forward_host: string, l_port: port, h_port: port)
{
	# c_type: channel type - see const policy for conversion table and function
	# wildcard: 0=no wildcard, 1=
	#	 "0.0.0.0"               -> wildcard v4/v6 if SSH_OLD_FORWARD_ADDR
	# 	 "" (empty string), "*"  -> wildcard v4/v6
	# 
	# forward_host: host to forward to
	# l_port: port being listened for forwards 
	# h_port: remote port to connect for forwards

	local CR:client_record = test_cid(sid,cid);

	if ( channel !in CR$channel_type )
		{
			CR$channel_type[channel] = "unknown";
		}

	print sshd_log, fmt("%.6f #%s %s-%s %s %s CHANNEL_SET_FWD_LISTENER %s wc:%s %s -> %s:%s",
		ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid), 
		cid, channel_name[c_type], wildcard, l_port, forward_host, h_port);
}

event channel_socks4_3(ts: time, version: string, sid: string, cid: count, channel: count, path: string, h_port: port, command: count, username: string)
{
	# decoded socks4 header
	# 
	# path: path for unix domain sockets, or host name for forwards 
	# h_port: remote port to connect for forwards
	# command: typically '1' - will get translation XXX
	# username: username provided by socks request, need not be the same as the uid

	local CR:client_record = test_cid(sid,cid);

	if ( channel !in CR$channel_type )
		{
			CR$channel_type[channel] = "unknown";
		}

	print sshd_log, fmt("%.6f #%s %s-%s %s %s CHANNEL_SOCKS4 command: %s socks4 to %s @ %s:%s",
		ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid), 
		cid, command, username, path, h_port);
}

event channel_socks5_3(ts: time, version: string, sid: string, cid: count, channel: count, path: string, h_port: port, command: count)
{
	# decoded socks5 header: this can be called multiple times per channel
	#  since the ports5 interface is somewhat more complicated
	# 
	# path: path for unix domain sockets, or host name for forwards 
	# h_port: remote port to connect for forwards
	# command: see const set for additional data

	local CR:client_record = test_cid(sid,cid);

	if ( channel !in CR$channel_type )
		{
			CR$channel_type[channel] = "unknown";
		}

	print sshd_log, fmt("%.6f #%s %s-%s %s %s CHANNEL_SOCKS5 command: %s[%s] socks5 to %s:%s",
		ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid), 
		cid, socks5_header_types[command], command, path, h_port);
}


event session_channel_request_3(ts: time, version: string, sid: string, cid: count, pid: int, channel: count, rtype: string)
{
	# This is a reuest for a channel type - the value will be filled
	#  in via the new_channel event, but this is where things are actually requested
	#
	# rtype values are: shell, exec, pty-req, x11-req, auth-agent@openssh.com, subsystem, env
	#

	local CR:client_record = test_cid(sid,cid);

	if ( channel !in CR$channel_type )
		{
			CR$channel_type[channel] = "unknown";
		}

	print sshd_log, fmt("%.6f #%s %s-%s %s %s SESSION_INPUT_CHAN_REQUEST %s",
		ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid),
		cid, to_upper(rtype));


	if ( to_upper(rtype) == "SUBSYSTEM" ) {
		# In an effort to track subsystem events like sftp, we need to get an index entry 
		#  for the cid lookup based on sid + pid
		# If there is a value in place we run it over - it should have been cleaned up
		#  in the session_exit event...

        	local split_cln = split(sid, /:/);
        	local t_sid = fmt("%s:%s", split_cln[2], split_cln[3]);

		cid_lookup[t_sid, pid] = cid;
	}

}

event session_do_auth_3(ts: time, version: string, sid: string, cid: count, atype: count, type_ret: count)
{
	# This is for version 1 of the protocol.  Seems like a great deal of work 
	#  for something that I really hope not to see ....
	#
	# Prepares for an interactive session.  This is called after the user has
	# been successfully authenticated.  During this message exchange, pseudo
	# terminals are allocated, X11, TCP/IP, and authentication agent forwardings
	# are requested, etc.
	#
	# type_ret: value indicating attempt/success/failure w/ 2/1/0

	local t_type_ret: string;

	if ( type_ret == 2 ) 
		t_type_ret = "ATTEMPT";
	else if ( type_ret == 1 )
		t_type_ret = "SUCCESS";
	else
		t_type_ret = "FAIL";

	# no channel data here
	local CR:client_record = test_cid(sid,cid);

	print sshd_log, fmt("%.6f #%s - %s SESSION_DO_AUTH %s %s",
		ts, CR$client_tag, print_sid(sid), channel_name[atype], t_type_ret);
}

event session_exit_3(ts: time, version: string, sid: string, cid: count, channel: count, pid: count, ststus: count)
{
	#
	local CR:client_record = test_cid(sid,cid);

	if ( channel !in CR$channel_type )
		{
			CR$channel_type[channel] = "unknown";
		}

	print sshd_log, fmt("%.6f #%s - %s SESSION_EXIT",
		ts, CR$client_tag, print_sid(sid));

	# on session exit, remove the entry asosciated with the subsystem
        local split_cln = split(sid, /:/);
        local t_sid = fmt("%s:%s", split_cln[2], split_cln[3]);
	
	if ( [t_sid,pid] in cid_lookup ) {
		delete cid_lookup[t_sid, pid];
	}

}

event session_input_channel_open_3(ts: time, version: string, sid: string, cid: count, tpe: count, ctype: string, rchan: int, rwindow: int, rmaxpack: int)
{
	# tpe: channel type as def in channel_name
	# ctype: one of { session, direct-tcpip, tun@openssh.com }
	# rchan: channel identifier for remote peer
	# rwindow: window size for channel
	# rmaxpack: max 'packet' for remote window 
	#

	local CR:client_record = test_cid(sid,cid);
# XXX 
# rchan is a guess - look and see what the actual values are
#	
	if ( int_to_count(rchan) !in CR$channel_type )
		{
			CR$channel_type[int_to_count(rchan)] = "unknown";
		}

	print sshd_log, fmt("%.6f #%s - %s %s SESSION_INPUT_CHAN_OPEN %s ctype %s rchan %d win %d max %d",
		ts, CR$client_tag, print_sid(sid),
		cid, CR$channel_type[int_to_count(rchan)], ctype, rchan, rwindow, rmaxpack);
		#cid, CR$channel_type[tpe], ctype, rchan, rwindow, rmaxpack);
}

event session_new_3(ts: time, version: string, sid: string, cid: count, pid: int, ver: string)
{
	local CR:client_record = test_cid(sid,cid);

	print sshd_log, fmt("%.6f #%s - %s %s SESSION_NEW %s",
		ts, CR$client_tag, print_sid(sid), cid, ver);

	# In an effort to track subsystem events like sftp, we need to get an index entry 
	#  for the cid lookup based on sid + pid
	# If there is a value in place we run it over - it should have been cleaned up
	#  in the session_exit event...

        #local split_cln = split(sid, /:/);
        #local t_sid = fmt("%s:%s", split_cln[2], split_cln[3]);
	#
	#cid_lookup[t_sid, pid] = cid;
}

event session_remote_do_exec_3(ts: time, version: string, sid: string, cid: count, channel: count, ppid: count, command: string)
{
	# This is called to fork and execute a command.  If another command is
	#  to be forced, execute that instead.

	local CR:client_record = test_cid(sid,cid);

	print sshd_log, fmt("%.6f #%s %s-%s %s %s SESSION_REMOTE_DO_EXEC %s",
		ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid), 
		cid, str_shell_escape(command));
}

event session_remote_exec_no_pty_3(ts: time, version: string, sid: string, cid: count, channel: count, ppid: count, command: string)
{
	# This is called to fork and execute a command when we have no tty.  This
	#  will call do_child from the child, and server_loop from the parent after
	#  setting up file descriptors and such.

	local CR:client_record = test_cid(sid,cid);

	print sshd_log, fmt("%.6f #%s %s-%s %s %s SESSION_REMOTE_EXEC_NO_PTY %s",
		ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid), 
		cid, str_shell_escape(command));
}

event session_remote_exec_pty_3(ts: time, version: string, sid: string, cid: count, channel: count, ppid: count, command: string)
{
	# This is called to fork and execute a command when we have a tty.  This
	#  will call do_child from the child, and server_loop from the parent after
	#  setting up file descriptors, controlling tty, updating wtmp, utmp,
	#  lastlog, and other such operations.

	local CR:client_record = test_cid(sid,cid);

	print sshd_log, fmt("%.6f #%s %s-%s %s %s SESSION_REMOTE_EXEC_PTY %s",
		ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid), cid, str_shell_escape(command));

}

event session_request_direct_tcpip_3(ts: time, version: string, sid: string, cid: count, channel: count, originator: string, orig_port: port, target: string, target_port: port, i: count)
{
	local CR:client_record = test_cid(sid,cid);

	print sshd_log, fmt("%.6f #%s %s-%s %s %s SESSION_REQUEST_DIRECT_TCPIP %s:%s -> %s:%s",
		ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid), cid, 
		originator, orig_port, target, target_port);

}

event session_tun_init_3(ts: time, version: string, sid: string, cid: count, channel: count, mode: count)
{
	# mode = { SSH_TUNMODE_POINTOPOINT | SSH_TUNMODE_ETHERNET }
	local CR:client_record = test_cid(sid,cid);

	if ( channel !in CR$channel_type )
		{
			CR$channel_type[channel] = "unknown";
		}

	print sshd_log, fmt("%.6f #%s %s-%s %s %s SESSION_TUN_INIT %s",
		ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid),
		cid, tunnel_type[mode]);

}

event session_x11fwd_3(ts: time, version: string, sid: string, cid: count, channel: count, display: string)
{
	# the string 'display' is generated from the following c code snippet:
	# session.c: 
	#	snprintf(display, sizeof display, "%.400s:%u.%u", hostname,
	#	    s->display_number, s->screen);

	local CR:client_record = test_cid(sid,cid);

	if ( channel !in CR$channel_type )
		{
			CR$channel_type[channel] = "unknown";
		}

	print sshd_log, fmt("%.6f #%s %s-%s %s %s SESSION_X11FWD_3 %s",
		ts, CR$client_tag, channel,CR$channel_type[channel], print_sid(sid),
		cid, display);

}


event sshd_connection_end_3(ts: time, version: string, sid: string, cid: count, r_addr: addr, r_port: port, l_addr: addr, l_port: port)
{
	local CR:client_record = test_cid(sid,cid);

	print sshd_log, fmt("%.6f #%s - %s %s SSHD_CONNECTION_END %s:%s -> %s:%s",
		ts, CR$client_tag, print_sid(sid), cid, 
		r_addr, r_port, l_addr, l_port);

}
event sshd_connection_start_3(ts: time, version: string, sid: string, cid: count, int_list: string, r_addr: addr, r_port: port, l_addr: addr, l_port: port, i: count)
{
	local CR:client_record = test_cid(sid,cid);

	print sshd_log, fmt("%.6f #%s - %s %s SSHD_CONNECTION_START %s:%s -> %s:%s",
		ts, CR$client_tag, print_sid(sid), cid,
		r_addr, r_port, l_addr, l_port);

	print sshd_log, fmt("%.6f #%s - %s %s SSHD_CONNECTION_START %s",
		ts, CR$client_tag, print_sid(sid), cid, int_list);

}

event sshd_exit_3(ts: time, version: string, sid: string, h: addr, p: port)
{
	local t_sid: server_record;
	t_sid = test_sid(sid);

	print sshd_log, fmt("%.6f - - - %s SSHD_EXIT %s:%s lifetime: %s sec",
		ts, print_sid(sid), h, p, ts - t_sid$start_time);
}

event sshd_restart_3(ts: time, version: string, sid: string, h: addr, p: port)
{
	local t_sid: server_record;
	t_sid = test_sid(sid);

	print sshd_log, fmt("%.6f - - - %s SSHD_RESTART %s:%s lifetime: %s sec",
		ts, print_sid(sid), h, p, ts - t_sid$start_time);

	t_sid$start_time = ts;
	s_records[sid] = t_sid;
}



event sshd_server_heartbeat_3(ts: time, version: string, sid: string,  dt: count)
	{

	# We start with an odd check - if the server record is not in the state table
	#  it might have been reaped from being shutdown.  In that case, just bail
	#  rather than creating a new server record.
	if ( sid !in s_records )
		return; 

	local SR:server_record = test_sid(sid);
	local isRemote: bool = is_remote_event();
	local ts_d:double = time_to_double(ts);

	# heartbeat_seen: 
	#  0 = NULL - default start value
	#  1 = remote trigger
	#  2 = timeout state
	#
	# add lock - 
	#  if external + lock -> do not reschedule
	#  if internal + lock -> schedule
	#  if external + nolock + first seen -> schedule
	#  if internal + nolock -> schedule

	# This should not normally trigger, but if the heartbeat event is local
	#  to this bro instance and a heartbeat has not yet been seen we bail.
	if ( (SR$heartbeat_seen == 0) && (! isRemote) ) {
		return;
		}

	# 
	if ( (SR$heartbeat_seen == 1 ) && (! isRemote) ) {

		# Interval test
		if ( ( ts_d - SR$heartbeat_last ) > interval_to_double(heartbeat_timeout) ) {
			
			# A sufficient interval of time has passed that we are interested
			#  in what is going on.  heartbeat_tmeout = 300 sec by default
			
			NOTICE([$note=SSHD_Heartbeat,
				$msg=fmt("Lost communication to %s, dt=%s",
					sid, ts_d - SR$heartbeat_last)]);
			
			# reset this value to avoid redundant notices
			SR$heartbeat_seen = 2;

		} # End interval test

	}
	# First time we have seen a heartbeat?
	else if ( (SR$heartbeat_seen == 0) && (isRemote) ) {

		SR$heartbeat_seen = 1;
		SR$heartbeat_last = ts_d;

		NOTICE([$note=SSHD_NewHeartbeat,
			$msg=fmt("New communication from %s", sid)]);
	}

	else if ( (SR$heartbeat_seen == 2) && isRemote) {

		SR$heartbeat_seen = 1;

	}

	# Reset the heartbeat_last value and save
	SR$heartbeat_last = ts_d;
	s_records[sid] = SR;

	# schedule the next heartbeat test
	if ( (( ! SR$heartbeat_lock ) && ( isRemote)) || ( ! isRemote )) {

		local hb_sched:interval = heartbeat_timeout + rand(10) * 1 sec;
		local new_time = double_to_time( interval_to_double(hb_sched) + ts_d );

		schedule hb_sched { sshd_server_heartbeat_3(new_time, version, sid, 0) };
		SR$heartbeat_lock = T;
	}
}

event sshd_start_3(ts: time, version: string, sid: string, h: addr, p: port)
{
	local t_sid: server_record;
	t_sid = test_sid(sid);

	t_sid$start_time = ts;
	s_records[sid] = t_sid;

	print sshd_log, fmt("%.6f - - %s SSHD_START %s:%s",
		ts, print_sid(sid), h, p);
}


